﻿using System;
using System.Linq;
using Castle.Core.Logging;
using Caramel;
using Microsoft.Practices.ServiceLocation;
using System.Collections.Generic;

namespace Caramel.ServiceBus
{
	public class ServiceBus : IServiceBus
	{
		public ServiceBus()
		{
			this.Logger = NullLogger.Instance;
		}

		public ILogger Logger { get; set; }

		public ICallback Send<T>(T command) where T : ICommand
		{
			return this.Send(new[] { command });
		}

		public ICallback Send<T>(params T[] commands) where T : ICommand
		{
			LocalStorage.Current["__CurrentCallback__"] = new Callback();

			this.
				EnsureUnitOfWork(
					() =>
					{
						foreach (var command in commands)
						{
							if (this.Logger.IsDebugEnabled)
							{
								this.Logger.DebugFormat("Handling command of type {0}: {1}", command.GetType().FullName, command.ToString());
							}
							//else
							//{
							//	this.Logger.InfoFormat("Handling command of type {0}.", command.GetType().FullName);
							//}

							var commandType = command.GetType();

							// TODO: look for handlers based on all interfaces implemented by command??

							var handlerType =
								typeof(ICommandHandler<>).
									MakeGenericType(command.GetType());

							var handlers = ServiceLocator.Current.GetAllInstances(handlerType);

							foreach (var handler in handlers)
							{
								this.Logger.InfoFormat("Handling command of type {0} using handler of type {1}.", command.GetType().FullName, handler.GetType().FullName);

								handlerType.
									GetMethod("Handle").
									Invoke(handler, new object[] { command });
							}
						}
					}
				);

			return LocalStorage.Current["__CurrentCallback__"] as Callback;
		}

		public void Return(object value)
		{
			var callback = LocalStorage.Current["__CurrentCallback__"] as Callback;

			callback.Result.ReturnValue = value;
		}

		public void Reply(object message)
		{
			var callback = LocalStorage.Current["__CurrentCallback__"] as Callback;

			callback.Result.Messages = callback.Result.Messages.Union(new[] { message }).ToArray();
		}

		public void Publish<T>(T @event) where T : IEvent
		{
			this.Publish(new[] { @event });
		}

		public void Publish<T>(params T[] events) where T : IEvent
		{
			this.
				EnsureUnitOfWork(
					() =>
					{
						foreach (var @event in events)
						{
							if (this.Logger.IsDebugEnabled)
							{
								this.Logger.DebugFormat("Handling event of type {0}: {1}", @event.GetType().FullName, @event.ToString());
							}
							//else
							//{
							//	this.Logger.InfoFormat("Handling event of type {0}.", @event.GetType().FullName);
							//}

							var handlers = ServiceLocator.Current.GetAllInstances<IEventHandler<T>>();

							foreach (var handler in handlers)
							{
								this.Logger.InfoFormat("Handling event of type {0} using handler of type {1}.", @event.GetType().FullName, handler.GetType().FullName);

								handler.Handle(@event);
							}
						}
					}
				);
		}

		private void EnsureUnitOfWork(Action action)
		{
			var work = UnitOfWork.Current;
			var isRoot = false;

			try
			{
				if (work == null)
				{
					work = UnitOfWork.Begin();
					isRoot = true;
				}

				action();

				if (isRoot)
				{
					work.Commit();
				}
			}
			finally
			{
				if (isRoot)
				{
					work.Dispose();
				}
			}
		}
	}
}
